// C source file with functions to deal with integers

/***********************************************************
* functions to print the value of bean primite integer types
************************************************************/

// print_byte_num() function
umax print_byte_num(void * ptr, umax size) {
  if (size < 1)
    return 0;
  printf("%u", *((byte *) ptr));
  return size;
}
// print_byte_hex() function
umax print_byte_hex(void * ptr, umax size) {
  if (size < 1)
    return 0;
  printf("%02X", *((byte *) ptr));
  return size;
}
// print_char_num() function
umax print_char_num(void * ptr, umax size) {
  if (size < 1)
    return 0;
  printf("%d", *((char *) ptr));
  return size;
}
// print_umax_num() function
umax print_umax_num(void * ptr, umax size) {
  if (size < sizeof(umax))
    return 0;
  printf("%llu", *((umax *) ptr));
  return size;
}
// print_umax_hex() function
umax print_umax_hex(void * ptr, umax size) {
  if (size < sizeof(umax))
    return 0;
  printf("%08llX", *((umax *) ptr));
  return size;
}
// print_smax_num() function
umax print_smax_num(void * ptr, umax size) {
  if (size < sizeof(smax))
    return 0;
  printf("%lld", *((smax *) ptr));
  return size;
}

/***********************************************
* powerof functions for signed/unsigned integers
************************************************/

// powerof_uint() function
// function to calculate the power of an unsigned integer to an unsigned
// exponent (an unsigned integer that is limited to umax container)
umax powerof_uint(umax base, byte exponent)
{
  // base equals 0
  if (base == 0)
    return 0;
  // exponent equals 0
  if (exponent == 0)
    return 1;
  
  // otherwise, calculate (base ^ (exponent))
  umax result = base;
  for (umax i = 2; i <= exponent; i++)
    result *= base;  
  return result;
}

// powerof_sint() function
// function to calculate the power of a signed integer to an unsigned
// exponent (a signed integer limited to smax container)
smax powerof_sint(smax base, byte exponent)
{
  // base equals 0
  if (base == 0)
    return 0;
  // exponent equals 0
  if (exponent == 0)
    return 1;
  
  // calculate result
  smax result = 1;
  for (umax i = 1; i <= exponent; i++)
    result *= base;  
  return result;
}

// get_max_uint() function
// return the maximum of 2 umaxs
umax get_max_uint(umax uint_1, umax uint_2)
{
  return uint_1 > uint_2 ? uint_1 : uint_2;
}

// get_min_uint() function
// return the minimum of 2 umaxs
umax get_min_uint(umax uint_1, umax uint_2)
{
  return uint_1 > uint_2 ? uint_2 : uint_1;
}

/********************************************************************************
* functions to calculate some types of integers from bits in the BIT_HOLDER array
*********************************************************************************/

/*********
* UNSIGNED
* Example: 10011101 (binary, 8 bits long)
* 1*(2)^7 + 0*(2)^6 + 0*(2)^5 + 1*(2)^4 + 1*(2)^3 + 1*(2)^2 + 0*(2)^1 + 1*(2)^0 = 157 (decimal)
***********************************************************************************************/

// bits_to_std_uint() function
// function to calculate an unsigned integer using the standard
// calculation method. It will read the bits on the BIT_HOLDER array
// (overflow will occur if the integer calculated goes outside umax!)
umax bits_to_std_uint(umax bit_holder_pos)
{
  umax result = 0;
  umax int_bit_size = BIT_HOLDER_LENGTH - bit_holder_pos;
  for (umax i = 0; i < int_bit_size; i++)
    result += BIT_HOLDER[BIT_HOLDER_LENGTH - i - 1] * powerof_uint(2, i);
  return result;
}

// tbh idk about another unsigned representation, if you do, let me know

/*******
* SIGNED
* Info about each representation here
* https://en.wikipedia.org/wiki/Signed_number_representations
*************************************************************/

// bits_to_sign_mag_sint() function
// treat the n-1 bits to the right of the binary number as an unsigned integer
// then add the sign from the leftmost bit to the left (1 = minus, 0 = plus)
smax bits_to_sign_mag_sint(umax bit_holder_pos)
{
  smax result = 0;
  umax int_bit_size = BIT_HOLDER_LENGTH - bit_holder_pos;
  
  // get the magnitude of the number
  for (umax i = 0; i < int_bit_size - 1; i++)
    result += BIT_HOLDER[BIT_HOLDER_LENGTH - i - 1] * powerof_uint(2, i);
  
  // get the sign
  result *= BIT_HOLDER[BIT_HOLDER_LENGTH - int_bit_size] == 1 ? -1 : 1; 
  
  return result;
}

// bits_to_1_compl_sint() function
// is like 2's compl but with a twist (what a description by me)
smax bits_to_1_compl_sint(umax bit_holder_pos)
{
  smax result = 0;
  umax int_bit_size = BIT_HOLDER_LENGTH - bit_holder_pos;
  
  // get the substraction done already
  if (BIT_HOLDER[BIT_HOLDER_LENGTH - int_bit_size] == 1)
    result -= 1 * powerof_uint(2, int_bit_size - 1) - 1;
    
  // calculate the rest of the number as an unsigned int
  for (umax i = 0; i < int_bit_size - 1; i++)
      result += BIT_HOLDER[BIT_HOLDER_LENGTH - i - 1] * powerof_uint(2, i);  
  
  return result;
}

// bits_to_2_compl_sint() function
// the one that is used on most systems from today 
smax bits_to_2_compl_sint(umax bit_holder_pos)
{
  smax result = 0;
  umax int_bit_size = BIT_HOLDER_LENGTH - bit_holder_pos;
  
  // get the substraction done already
  if (BIT_HOLDER[BIT_HOLDER_LENGTH - int_bit_size] == 1)
    result -= 1 * powerof_uint(2, int_bit_size - 1);
  
  // get the rest of the number as an unsigned integer and add it to result
  for (umax i = 0; i < int_bit_size - 1; i++)
    result += BIT_HOLDER[BIT_HOLDER_LENGTH - i - 1] * powerof_uint(2, i);   
  
  return result;
}

// bits_to_off_bin_sint() function
// 0 is just placed at the 1000....0000 binary number 
smax bits_to_off_bin_sint(umax bit_holder_pos)
{
  smax result = 0;
  umax int_bit_size = BIT_HOLDER_LENGTH - bit_holder_pos;
  
  // get the bias and subtract it right away
  result -= 1 * powerof_uint(2, int_bit_size - 1) - 1;
  
  // get the complete number as an unsigned integer and add it to result
  for (umax i = 0; i < int_bit_size; i++)
      result += BIT_HOLDER[BIT_HOLDER_LENGTH - i - 1] * powerof_uint(2, i);  
  
  return result;
}

// bits_to_bminus2_sint() function
// just like the unsigned integer calculation but using the -2 base
smax bits_to_bminus2_sint(umax bit_holder_pos)
{
  smax result = 0;
  umax int_bit_size = BIT_HOLDER_LENGTH - bit_holder_pos;
  
  // calculate the number as an unsigned integer but use base -2 instead of +2
  for (umax i = 0; i < int_bit_size; i++)
    result += BIT_HOLDER[BIT_HOLDER_LENGTH - i - 1] * powerof_sint(-2, i);
  
  return result;
}

/**********************************************************************
* functions to calculate different type of integers from bits in memory
***********************************************************************/

// UNSIGNED

// calc_mem_std_uint() function
umax calc_mem_std_uint(umax byte_cnt, umax lshifts, umax bit_cnt, umax lpad_cnt, void * src, ENDIAN_T endian)
{
  return bits_to_std_uint(get_byte_bits(byte_cnt, lshifts, bit_cnt, lpad_cnt, src, endian));
}

// SIGNED

// calc_mem_sign_mag_sint() function
smax calc_mem_sign_mag_sint(umax byte_cnt, umax lshifts, umax bit_cnt, umax lpad_cnt, void * src, ENDIAN_T endian)
{
  return bits_to_sign_mag_sint(get_byte_bits(byte_cnt, lshifts, bit_cnt, lpad_cnt, src, endian));
}

// calc_mem_1_compl_sint() function
smax calc_mem_1_compl_sint(umax byte_cnt, umax lshifts, umax bit_cnt, umax lpad_cnt, void * src, ENDIAN_T endian)
{
  return bits_to_1_compl_sint(get_byte_bits(byte_cnt, lshifts, bit_cnt, lpad_cnt, src, endian));
}

// calc_mem_2_compl_sint() function
smax calc_mem_2_compl_sint(umax byte_cnt, umax lshifts, umax bit_cnt, umax lpad_cnt, void * src, ENDIAN_T endian)
{
  return bits_to_2_compl_sint(get_byte_bits(byte_cnt, lshifts, bit_cnt, lpad_cnt, src, endian));
}

// calc_mem_off_bin_sint() function
smax calc_mem_off_bin_sint(umax byte_cnt, umax lshifts, umax bit_cnt, umax lpad_cnt, void * src, ENDIAN_T endian)
{
  return bits_to_off_bin_sint(get_byte_bits(byte_cnt, lshifts, bit_cnt, lpad_cnt, src, endian));
}

// calc_mem_bminus2_sint() function
smax calc_mem_bminus2_sint(umax byte_cnt, umax lshifts, umax bit_cnt, umax lpad_cnt, void * src, ENDIAN_T endian)
{
  return bits_to_bminus2_sint(get_byte_bits(byte_cnt, lshifts, bit_cnt, lpad_cnt, src, endian));
}

/********************************************
* functions to get usual integers from memory
* (not byte aligned, it is assumed the system
* uses these types of integers)
* - standard unsigned integer
* - 2-complement signed integer
*******************************/

// get_uint() function
// function to get a standard unsigned integer from memory without a manual calculation
// method (assumes the system uses the std unsigned integer representation)
// be careful with bit sizes that overflow umax!
umax get_uint(umax byte_cnt, umax lshifts, umax bit_cnt, umax lpad_cnt, void * src, ENDIAN_T endian)
{
  // variable to return
  umax result = 0;  
  // get the bits from src
  get_byte_bits(byte_cnt, lshifts, bit_cnt, lpad_cnt, src, endian);
  // copy the bits into result
  cp_mem_bytes(BYTE_HOLDER + BYTE_HOLDER_LENGTH - sizeof(umax), sizeof(umax), &result);
  // modify byte order depending on SYS_ENDIAN
  if (SYS_ENDIAN == LITTLE)
    reverse_array(&result, sizeof(umax), sizeof(byte)); 
  // ^ as the bytes are already in big endian there is no need to
  // manage them after the copy process if the system is in big endian  
  return result;
}

// get_sint() function
// same as the above function but for signed integers
// (2-complement ints are assumed to be used by the system)
smax get_sint(umax byte_cnt, umax lshifts, umax bit_cnt, umax lpad_cnt, void * src, ENDIAN_T endian)
{  
  // variable to return and temp var
  smax result = 0, temp = 0;
  
  // get the bits from src
  result = get_uint(byte_cnt, lshifts, bit_cnt, lpad_cnt, src, endian);  
  
  // get the left binary digits of the number container to be 1 and
  // make the binary digits ocupied by the actual integer to 0
  // then, add that binary digit result to the integer (to result)
  temp = (~ result) >> bit_cnt;	
  temp = (temp << bit_cnt) + result;      
  // check which one of the variables (result or temp) is outside the
  // signed integer range and return the one thats in (compare magnitude)
  result = (~ temp) > result ? result : temp;
  // bruh moment
  return result;
}

/****************************************
* functions to get byte aligned and 
* specific sized usual integers in memory
* I will call them "standard" (common) 
* integers (8, 16 and 32 bits long ints)
*****************************************/

// std_int_check() function
// function to be used in the below functions
bool std_int_check(umax bit_cnt);
bool std_int_check(umax bit_cnt)
{
  // return 0 if the bit size is not 8, 16 or 32
  if ((bit_cnt % 8) != 0 || bit_cnt > 32)
    return 0;
  return 1;
}

// get_std_uint() function
// special case of the get_uint() function. bit_size is 
// limited to 8, 16 and 32 and the integer bits are byte aligned
umax get_std_uint(umax bit_cnt, void * src, ENDIAN_T endian)
{
  // get the unsigned integer if the bit size is valid
  if (std_int_check(bit_cnt))
    return get_uint(0, 0, bit_cnt, 0, src, endian);
  return 0;
}

// get_std_sint() function
// same as get_std_uint() but for signed integers
smax get_std_sint(umax bit_cnt, void * src, ENDIAN_T endian)
{
  // get the signed integer if the bit size is valid
  if (std_int_check(bit_cnt))
    return get_sint(0, 0, bit_cnt, 0, src, endian);
  return 0;
}

// functions to print std integers

// print_uint8() function
umax print_uint8(void * ptr, umax size)
{
  return print_byte_num(ptr, size);
}

// print_uint16be() function
umax print_uint16be(void * ptr, umax size)
{
  if (size < 2)
    return 0;
  printf("%llu", get_std_uint(16, ptr, BIG));
  return size;
}

// print_uint16le() function
umax print_uint16le(void * ptr, umax size)
{
  if (size < 2)
    return 0;
  printf("%llu", get_std_uint(16, ptr, LITTLE));
  return size;
}

// print_uint32be() function
umax print_uint32be(void * ptr, umax size)
{
  if (size < 4)
    return 0;
  printf("%llu", get_std_uint(32, ptr, BIG));
  return size;
}

// print_uint32le() function
umax print_uint32le(void * ptr, umax size)
{
  if (size < 4)
    return 0;
  printf("%llu", get_std_uint(32, ptr, LITTLE));
  return size;
}

// print_sint8() function
umax print_sint8(void * ptr, umax size)
{
  return print_char_num(ptr, size);
}

// print_sint16be() function
umax print_sint16be(void * ptr, umax size)
{
  if (size < 2)
    return 0;
  printf("%llu", get_std_sint(16, ptr, BIG));
  return size;
}

// print_sint16le() function
umax print_sint16le(void * ptr, umax size)
{
  if (size < 2)
    return 0;
  printf("%llu", get_std_sint(16, ptr, LITTLE));
  return size;
}

// print_sint32be() function
umax print_sint32be(void * ptr, umax size)
{
  if (size < 4)
    return 0;
  printf("%llu", get_std_sint(32, ptr, BIG));
  return size;
}

// print_sint32le() function
umax print_sint32le(void * ptr, umax size)
{
  if (size < 4)
    return 0;
  printf("%llu", get_std_sint(32, ptr, LITTLE));
  return size;
} 

// Comparison functions

// umax_equal() function
// function to check if 2 umax integers are the same
bool umax_equal(void * umax1, void * umax2)
{
  return ((umax *) umax1)[0] == ((umax *) umax2)[0];
}
